#include "flvserver.h"

#include <fcntl.h>
#include <pthread.h>
#include <signal.h>

#include <chrono>
#include <sstream>

#include "CUitl.h"
#include "json/json.h"

typedef struct _stflvSessionInfo {
  flv_muxer_t* m;
  std::list<std::string>* queue;
  std::mutex* mutex;
  uint32_t pts;
  char url[256];
} stflvSessionInfo;

char* pFrameData_ = NULL;
FlvStreamServer::FlvStreamServer(int port, std::string rtspurl) {
  RtspUrl_ = rtspurl;
  if (!pFrameData_) {
    pFrameData_ = (char*)malloc(1024 * 1024);
  }
  httpport_ = std::to_string(port);
}
FlvStreamServer::~FlvStreamServer() {
  if (pFrameData_) {
    free(pFrameData_);
  }
}
void FlvStreamServer::run() {
  mg_mgr_init(&mgr_, nullptr);
  nc_ = mg_bind(&mgr_, httpport_.c_str(), FlvStreamServer::eventHandlerWrapper);
  nc_->user_data = this;
  mg_set_protocol_http_websocket(nc_);
  printf("Starting Rtsp Stream server on port %s\n", httpport_.c_str());
  for (;;) {
    mg_mgr_poll(&mgr_, 10);
  }
  mg_mgr_free(&mgr_);
}

void FlvStreamServer::eventHandlerWrapper(struct mg_connection* nc, int ev,
                                          void* ev_data) {
  FlvStreamServer* server = (FlvStreamServer*)(nc->user_data);
  server->eventHandler(nc, ev, ev_data);
}

bool FlvStreamServer::HttpSend(struct mg_connection* nc, const char* pData,
                               int len) {
  mg_send(nc, pData, len);
  return true;
}

bool FlvStreamServer::SendDataFlv(struct mg_connection* nc, const char* pData,
                                  int len, bool needheader) {
  if (!needheader) {
    return HttpSend(nc, pData, len);
  }
  std::stringstream decSize;
  decSize << std::dec << len;
  std::string chunkHeader = decSize.str() + "\r\n";
  HttpSend(nc, chunkHeader.c_str(), chunkHeader.size());
  if (pData) {
    HttpSend(nc, pData, len);
    HttpSend(nc, "\r\n", 2);
  }
  return true;
}
bool FlvStreamServer::SendFlvHeaderTag(struct mg_connection* nc) {
  uint8_t header[9];
  header[0] = 'F';  // FLV signature
  header[1] = 'L';
  header[2] = 'V';
  header[3] = 0x01;  // File version
  header[4] = 0x01;  // Type flags (audio & video)
  header[5] = 0x00;
  header[6] = 0x00;
  header[7] = 0x00;  // File version
  header[8] = 0x09;
  SendDataFlv(nc, (const char*)header, sizeof(header));
  header[0] = 0x00;  // FLV signature
  header[1] = 0x00;
  header[2] = 0x00;
  header[3] = 0x00;  // File version
  SendDataFlv(nc, (const char*)header, 4);
  return true;
}
void FlvStreamServer::eventHandler(struct mg_connection* nc, int ev,
                                   void* ev_data) {
  if (ev == MG_EV_POLL) {
    sendQueuedData();
  } else if (ev == MG_EV_HTTP_REQUEST) {
    struct http_message* hm = (struct http_message*)ev_data;
    std::cout << std::string(hm->uri.p, hm->uri.len) << std::endl;
    if (mg_vcmp(&hm->uri, "/live/stream.flv") == 0) {
      mg_printf(nc,
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: video/x-flv\r\n"
                "Connection: Keep-Alive\r\n"
                "Transfer-Encoding: chunked\r\n\r\n");
      SendFlvHeaderTag(nc);
      stflvSessionInfo* pSession =
          (stflvSessionInfo*)malloc(sizeof(stflvSessionInfo));
      memcpy(pSession->url, hm->uri.p, hm->uri.len);
      pSession->queue = new std::list<std::string>;
      pSession->mutex = new std::mutex;
      connection_map_[nc] = (void*)pSession;
      pSession->m = CreateFlvMuxer(pSession);
      RtspStart(RtspUrl_.c_str());
    } else if (mg_vcmp(&hm->uri, "/setrtspurl") == 0) {
      std::string body(hm->body.p, hm->body.len);
      std::cout << body << std::endl;
      Json::Value jsBody;
      CUitl::str2json(body, jsBody);
      RtspUrl_ = jsBody["rtsp_url"].asString();
      RtspStop();
      RtspStart(RtspUrl_.c_str());
      Json::Value rsq;
      rsq["code"] = 200;
      std::string data = CUitl::json2str(rsq);
      mg_printf(nc,
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: application/json\r\n"
                "Connection: close\r\n"
                "Content-Length: %u\r\n\r\n"
                "%s\r\n",
                (uint32_t)data.size(), data.c_str());
      nc->flags |= MG_F_SEND_AND_CLOSE;
    }

    else {
      static struct mg_serve_http_opts s_http_server_opts;
      s_http_server_opts.document_root = "www";  // Serve current directory
      s_http_server_opts.enable_directory_listing = "no";
      mg_serve_http(nc, (struct http_message*)ev_data, s_http_server_opts);
    }
  } else if (ev == MG_EV_CLOSE) {
    for (const auto& connection : connection_map_) {
      if (connection.first == nc) {
        stflvSessionInfo* pSession = (stflvSessionInfo*)connection.second;
        if (pSession) {
          pSession->queue->clear();
          flv_muxer_destroy(pSession->m);
          delete pSession->queue;
          delete pSession->mutex;
          free(pSession);
          connection_map_.erase(connection.first);
          break;
        }
      }
    }
    printf("connection_map_.size():%ld\n", connection_map_.size());
  }
}

void FlvStreamServer::sendQueuedData() {
  for (const auto& connection : connection_map_) {
    stflvSessionInfo* p = (stflvSessionInfo*)connection.second;
    if (!p) {
      printf("%s %d\n", __FUNCTION__, __LINE__);
      continue;
    }
    if (p->queue->empty()) continue;
    std::string data = p->queue->front();
    p->queue->pop_front();
    std::stringstream hexSize;
    hexSize << std::hex << data.size();
    std::string datatunck = hexSize.str() + "\r\n";
    datatunck.append(data);
    datatunck.append("\r\n");
    SendDataFlv(connection.first, datatunck.c_str(), datatunck.size(), false);
  }
}
void FlvStreamServer::sendFrame(const void* video_data, int len, int codec) {
  for (const auto& connection : connection_map_) {
    stflvSessionInfo* p = (stflvSessionInfo*)connection.second;
    static uint32_t pts;
    const uint8_t* nalu = (const uint8_t*)video_data + 4;
    flv_muxer_h264_nalu(p->m, nalu, len - 4, p->pts, p->pts);
    uint8_t type = *nalu & 0x1f;
    if (type > 0 && type < 6) {
      p->pts = gettimems();
    }
  }
}
int FlvStreamServer::OnFlvPack(void* flv, int type, const void* data,
                               size_t bytes, uint32_t timestamp) {
  int len =
      flv_writer_input_mem(flv, type, data, bytes, timestamp, pFrameData_);
  stflvSessionInfo* pFlv = (stflvSessionInfo*)flv;
  std::string strdata = std::string((const char*)pFrameData_, len);
  pFlv->queue->push_back(strdata);
  return 0;
}
flv_muxer_t* FlvStreamServer::CreateFlvMuxer(void* pUsrData) {
  flv_muxer_t* m = flv_muxer_create(&FlvStreamServer::OnFlvPack, pUsrData);
  struct flv_metadata_t metadata = {0};
  metadata.videocodecid = 7;
  metadata.videodatarate = 4096.0;
  metadata.framerate = 30;
  metadata.width = 1280;
  metadata.height = 768;
  metadata.audiocodecid = 0;

  // Meta数据暂时没啥用，主要是切换场景的时候会导致分辨率问题？
  flv_muxer_metadata(m, &metadata);
  return m;
}

uint32_t FlvStreamServer::gettimems() {
  struct timeval tmnow;
  gettimeofday(&tmnow, NULL);
  uint32_t ms = tmnow.tv_sec * 1000 + tmnow.tv_usec / 1000;
  return ms;
}

int FlvStreamServer::RTSPSourceCall(EASY_FRAME_INFO_T frameinfo,
                                    void* userdata) {
  FlvStreamServer* server = (FlvStreamServer*)userdata;
  if (frameinfo.frameType == EASY_RTP_VIDEO) {
    server->sendFrame(frameinfo.framebuff, frameinfo.framesize);
  }
  return 0;
}

void* FlvStreamServer::RtspProcess(void* args) {
  FlvStreamServer* server = (FlvStreamServer*)args;
  while (server->RtspRunnig_) {
    MyRTSP_Run(server->hRTSPHandle_);
  }
  printf("Rtsp thread Finish\n");
  return nullptr;
}

int FlvStreamServer::RtspStart(const char* url) {
  if (RtspRunnig_) {
    return 0;
  }
  printf("%s %d %s\n", __FUNCTION__, __LINE__, url);
  RtspRunnig_ = true;
  MyRTSP_Init(&hRTSPHandle_);
  MyRTSP_SetCallback(hRTSPHandle_, RTSPSourceCall, (void*)this);
  MyRTSP_OpenStream(hRTSPHandle_, url, EASY_RTP_OVER_TCP, 0);
  pthread_create(&rtsp_thd_, 0, RtspProcess, (void*)this);
  return 0;
}
int FlvStreamServer::RtspStop() {
  printf("%s %d\n", __FUNCTION__, __LINE__);
  RtspRunnig_ = false;
  MyRTSP_Deinit(hRTSPHandle_);
  pthread_join(rtsp_thd_, NULL);
  return 0;
}